//
//  Store.swift
//  FearfulNotification
//
//  Created by 筱鹏 on 2019/3/28.
//  Copyright © 2019 筱鹏. All rights reserved.
//

import UIKit
import CoreData

struct StoreError:Error {
    enum ErrorKind: String {
        case getManagedObjectContext    = "获取managedObjectContext失败"
        case persistentSQliteError      = "数据库关联失败"
        case insertNewObjectError        = "插入新数据失败"
    }
    let context: String
    let kind: ErrorKind
}

class Store: NSObject {
    typealias ObjectClosure = (NSManagedObject) -> Void
    typealias FinishClosure = (Bool) -> Void
    typealias ChangeObjectsClosure = (NSManagedObject, Int) -> Void
    
    static let manager = Store()

    private let workContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    private let mainContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    private override init() {
        super.init()
        registerNotification()
    }
    
    private func persistent() throws -> NSPersistentStoreCoordinator {
        let persis = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        let storeUrl:URL = Path.documentUrl().appendingPathComponent("FearfulNotification.sqlite")
        let options = [NSInferMappingModelAutomaticallyOption:true,
                       NSMigratePersistentStoresAutomaticallyOption:true]
        guard let _ = try? persis.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeUrl, options: options) else {
            throw StoreError(context: "\(storeUrl)", kind: .persistentSQliteError)
        }
        return persis
    }
    
    private lazy var managedObjectModel: NSManagedObjectModel = {
        // 获取 CoreData模型文件
        let path = Bundle.main.path(forResource: "FearfulNotification", ofType: "momd")
        let url = URL(fileURLWithPath: path!)
        let model = NSManagedObjectModel(contentsOf: url)
        return model!
    }()
    
    
    private lazy var readContext: NSManagedObjectContext = {
        do {
            let value = try self.persistent()
            mainContext.persistentStoreCoordinator = value
        } catch {
            Event.error("persistent \(error)")
            fatalError("persistent \(error)")
        }
        return mainContext
    }()
    
    private lazy var updateContext: NSManagedObjectContext = {
        do {
            let value = try self.persistent()
            workContext.persistentStoreCoordinator = value
        } catch {
            Event.error("persistent \(error)")
            fatalError("persistent \(error)")
        }
        return workContext
    }()
}

//TODO: 私有的增删改查 调用 updateContext
extension Store {

    private func save() {
        let context = self.updateContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                Event.error("save error \(error)")
                fatalError("save error \(error)")
            }
        }
    }
    
    /**
     *  查询NSManagedObject（根据objectID）
     *
     *  @param objectID An object ID.
     *
     *  @return The object for the specified ID.
     */
    private func object(of objId: NSManagedObjectID) -> NSManagedObject {
        do {
            let obj = try self.updateContext.existingObject(with: objId)
            return obj
        } catch {
            Event.error("get managedObject with id \(error)")
            fatalError("get managedObject with id \(error)")
        }
    }
    
    /**
     *  删除NSManagedObject（根据objectID）
     *
     *  @param objectID an objectID
     */
    private func delete(objId: NSManagedObjectID) {
        let obj = self.object(of: objId)
        self.delete(obj)
    }
    
    /**
     *  条件查询（通过fetchRequest）
     *
     *  @param request 请求条件模型
     *  @return 返回符合NSFetchRequest条件的NSManagedObject数组
     */
    private func fetch(request:NSFetchRequest<NSFetchRequestResult>) -> [Any] {
        var result:[Any] = []
        do {
            result = try self.updateContext.fetch(request)
        } catch {
            Event.error("store executeFetchRequest error \(error)")
            fatalError("store executeFetchRequest error \(error)")
        }
        return result
    }
    
    /**
     *  删除NSManagedObject（根据NSManagedObject）
     *
     *  @param objectID an NSManagedObject
     */
    private func delete(_ obj: NSManagedObject) {
        self.updateContext.delete(obj)
    }
    
    /** updateContext 执行的 Closure */
    private func execute(_ action: @escaping ()-> Void) {
        self.updateContext.perform {
            action()
        }
    }
}


//TODO: NotificationMethod
extension Store {
    
    private func registerNotification() {
        NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(noti:)), name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil)
    }
    
    @objc private func contextDidSave(noti: Notification) {
        self.readContext.mergeChanges(fromContextDidSave: noti)
    }
}

//TODO: 更新插入删除方法
extension Store {
    
    /**
     *  插入一条ManageObject对象
     *
     *  @param entityName 实体名称
     *  @param finish 保存完成回调
     *  @param completion 数据回调
     */
    func performInsert(_ entityName:String, _ finish: @escaping FinishClosure = {_ in}, _ completion: @escaping ObjectClosure) {
        execute {
            let entity = NSEntityDescription.insertNewObject(forEntityName: entityName, into: self.updateContext)
            completion(entity)
            self.save()
            safeMainQueueAsync {
                finish(true)
            }
        }
    }
    
    /**
     *  修改NSManagedObject
     *
     *  @param entity an NSManagedObject
     *  @param finish 保存完成回调
     *  @param change 所有修改操作在此回调进行
     */
    func performChange(objectId: NSManagedObjectID, _ finish: @escaping FinishClosure = {_ in}, _ change: @escaping ObjectClosure) {
        execute {
            let entity = self.object(of: objectId)
            change(entity)
            self.save()
            safeMainQueueAsync {
                finish(true)
            }
        }
    }
    
    /**
     *  修改NSManagedObject
     *
     *  @param entitys an [NSManagedObject]
     *  @param finish 保存完成回调
     *  @param change 所有修改操作在此回调进行 (obj, obj所在的位置)
     *  @warning 在子线程调用change
     */
    func performChange(objectIds: [NSManagedObjectID], _ finish: @escaping FinishClosure = {_ in}, _ change: @escaping ChangeObjectsClosure) {
        execute {
            for idx in 0 ..< objectIds.count {
                let entity = self.object(of: objectIds[idx])
                change(entity, idx)
            }
            self.save()
            safeMainQueueAsync {
                finish(true)
            }
        }
    }
    
    /**
     *  删除NSManagedObject（根据objectID）
     *
     *  @param objectID an objectID
     *  @param finish 保存完成回调
     */
    func performDelete(objId: NSManagedObjectID, _ finish: @escaping FinishClosure = {_ in}) {
        execute {
            self.delete(objId: objId)
            self.save()
            safeMainQueueAsync {
                finish(true)
            }
        }
    }
    
    /**
     *  删除NSManagedObject（根据NSManagedObject）
     *
     *  @param obj an NSManagedObject
     *  @param finish 保存完成回调
     */
    func performDelete(entityName: String, _ predicate: String, _ finish: @escaping FinishClosure = {_ in}) {
        execute {
            let request = NSFetchRequest<NSFetchRequestResult>()
            let entityDesc = NSEntityDescription.entity(forEntityName: entityName, in: self.updateContext)
            request.entity = entityDesc
            if predicate.lengthOfBytes(using: .utf8) > 0 {
                request.predicate = NSPredicate(format: predicate)
            }
            let resultArr = self.fetch(request: request)
            if resultArr.count > 0 {
                for result in resultArr {
                    if let obj = result as? NSManagedObject {
                        self.delete(obj)
                    }
                }
            }
            self.save()
            safeMainQueueAsync {
                finish(true)
            }
        }
    }
}

//TODO: 查询方法 (只读)
extension Store {
    
    /**
     *  条件查询（通过fetchRequest）
     *
     *  @param request 请求条件模型
     *  @return 返回符合NSFetchRequest条件的NSManagedObject数组
     */
    func readFetch(_ request:NSFetchRequest<NSFetchRequestResult>) -> [Any] {
        var result:[Any] = []
        do {
            result = try self.readContext.fetch(request)
        } catch {
            Event.error("store executeFetchRequest error \(error)")
            fatalError("store executeFetchRequest error \(error)")
        }
        return result
    }
    
    /**
     *  查询某一条件下消息个数
     */
    func readFetchCount(_ request:NSFetchRequest<NSFetchRequestResult>) -> Int {
        do {
            return try self.readContext.count(for: request)
        } catch {
            Event.error("fetchCount error \(error)")
            fatalError("fetchCount error \(error)")
        }
    }
    
    /**
     *  条件查询（通过fetchRequest）
     *
     *  @param request 请求条件模型
     *
     *  @return 返回传入NSFetchRequest的NSFetchedResultsController
     */
    func readFetchResultController(_ request:NSFetchRequest<NSFetchRequestResult>) -> NSFetchedResultsController<NSFetchRequestResult>
    {
        return NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.readContext, sectionNameKeyPath: nil, cacheName: nil)
    }
}
